//==========================================================================
// Copyright (c) 2000-2008,  Elastos, Inc.  All Rights Reserved.
//==========================================================================

#include <ddk.h>

#include "ite8172.h"

struct It8172IsrDescriptors IRQ[NR_IRQS];

struct it8172_intc_regs volatile *It8172HwReg
        = (struct it8172_intc_regs volatile *)
            (KSEG1ADDR(IT8172_PCI_IO_BASE + IT_INTC_BASE));

void CDECL Ite8172DispatchIsr(
    irq_t cIrq, void *pvDevice, InterruptContext *pContext)
{
    int irq = 0;
    unsigned short intstatus, status;

    assert(2 == cIrq);

    intstatus = It8172HwReg->intstatus;

    if (intstatus & 0x8) {
        kprintf("Got NMI interrupt\n");
        DebugBreak();
        irq = 0;
    }

    if (intstatus & 0x4) {
        // PCI interrupt
        status = It8172HwReg->pci_req;
        while (!(status & 0x1)) {
            irq++;
            status >>= 1;
        }
        irq += IT8172_PCI_DEV_IRQ_BASE;
//        kprintf("pci int %d\n", irq);
        DoIrq(irq, pContext);
        irq = 0;
    }

    if (intstatus & 0x1) {
        // Local Bus interrupt
        status = It8172HwReg->lb_req;
        while (!(status & 0x1)) {
            irq++;
            status >>= 1;
        }
        irq += IT8172_LB_IRQ_BASE;
        kprintf("local bus int %d\n", irq);
        DoIrq(irq, pContext);
        irq = 0;
    }

    if (intstatus & 0x2) {
        // LPC interrupt
        /* Since some lpc interrupts are edge triggered,
         * we could lose an interrupt this way because
         * we acknowledge all ints at onces. Revisit.
         */
        status = It8172HwReg->lpc_req;
        It8172HwReg->lpc_req = 0; /* acknowledge ints */
        while (!(status & 0x1)) {
            irq++;
            status >>= 1;
        }
        irq += IT8172_LPC_IRQ_BASE;
//      kprintf("LPC int %d\n", irq);
        DoIrq(irq, pContext);
    }
}

IDeviceDriver * CDECL InitIt8172irq(uint_t uDeviceNo, void *pvParameter)
{
    // mask all interrupts
    It8172HwReg->lb_mask  = 0xffff;
    It8172HwReg->lpc_mask = 0xffff;
    It8172HwReg->pci_mask = 0xffff;
    It8172HwReg->nmi_mask = 0xffff;

    // make all interrupts level triggered
    It8172HwReg->lb_trigger  = 0;
    It8172HwReg->lpc_trigger = 0;
    It8172HwReg->pci_trigger = 0;
    It8172HwReg->nmi_trigger = 0;

    // active level setting
    // uart, keyboard, and mouse are active high
    It8172HwReg->lpc_level = (0x10 | 0x2 | 0x1000);
    It8172HwReg->lb_level |= 0x20;

    // keyboard and mouse are edge triggered
    It8172HwReg->lpc_trigger |= (0x2 | 0x1000);

#if 0
    // Enable this piece of code to make internal USB interrupt
    // edge triggered.
    It8172HwReg->pci_trigger |=
        (1 << (IT8172_USB_IRQ - IT8172_PCI_DEV_IRQ_BASE));
    It8172HwReg->pci_level &=
        ~(1 << (IT8172_USB_IRQ - IT8172_PCI_DEV_IRQ_BASE));

    /*
     * Enable external int line 2
     * All ITE interrupts are masked for now.
     */
#define EXT_IRQ0_TO_IP 2 /* IP 2 */

    unsigned long flags;
    save_and_cli(flags);
    enableIrq(1<<EXT_IRQ0_TO_IP);
    restore_flags(flags);
#endif

//    EnableIt8172Irq(IT8172_SERIRQ_1);       // enable keyboard irq
//    EnableIt8172Irq(IT8172_SERIRQ_2);       // unknown
//    EnableIt8172Irq(IT8172_SERIRQ_4);       // unknown
//    EnableIt8172Irq(IT8172_SERIRQ_9);       // unknown
//    EnableIt8172Irq(IT8172_SERIRQ_12);      // enable mouse irq
//    EnableIt8172Irq(IT8172_RTC_IRQ);        // RTC irq would happen if enabled
//    EnableIt8172Irq(IT8172_UART_IRQ);       // enable UART irq
//    EnableIt8172Irq(IT8172_SCR0_IRQ);       // enable SCR0 irq
//    EnableIt8172Irq(IT8172_SCR1_IRQ);       // enable SCR1 irq
//    EnableIt8172Irq(IT8172_PCI_INTA_IRQ);   // enable PCI slot A irq
//    EnableIt8172Irq(IT8172_PCI_INTB_IRQ);   // enable PCI slot B irq
//    EnableIt8172Irq(IT8172_PCI_INTC_IRQ);   // enable PCI slot C irq
//    EnableIt8172Irq(IT8172_PCI_INTD_IRQ);   // enable PCI slot D irq
    DzRegisterIsr(2, IPL1, &Ite8172DispatchIsr, NULL);

    return NULL;
}

void DisableIt8172Irq(unsigned int irq_nr)
{
    //kprintf("DisableIt8172Irq %d\n", irq_nr);

    if ((irq_nr >= IT8172_LPC_IRQ_BASE)
            && (irq_nr <= IT8172_SERIRQ_15)) {
        // LPC interrupt
        It8172HwReg->lpc_mask |= (1 << (irq_nr - IT8172_LPC_IRQ_BASE));
    }
    else if ((irq_nr >= IT8172_LB_IRQ_BASE)
            && (irq_nr <= IT8172_IOCHK_IRQ)) {
        // Local Bus interrupt
        It8172HwReg->lb_mask |= (1 << (irq_nr - IT8172_LB_IRQ_BASE));
    }
    else if ((irq_nr >= IT8172_PCI_DEV_IRQ_BASE)
            && (irq_nr <= IT8172_DMA_IRQ)) {
        // PCI and other interrupts
        It8172HwReg->pci_mask |= (1 << (irq_nr - IT8172_PCI_DEV_IRQ_BASE));
    }
    else if ((irq_nr >= IT8172_NMI_IRQ_BASE)
            && (irq_nr <= IT8172_POWER_NMI_IRQ)) {
        // NMI interrupts
        It8172HwReg->nmi_mask |= (1 << (irq_nr - IT8172_NMI_IRQ_BASE));
    }
    else {
        kprintf("DisableIt8172Irq: bad irq %d\n", irq_nr);
    }
}

void EnableIt8172Irq(unsigned int irq_nr)
{
    if ((irq_nr >= IT8172_LPC_IRQ_BASE)
            && (irq_nr <= IT8172_SERIRQ_15)) {
        // LPC interrupt
        It8172HwReg->lpc_mask &= ~(1 << (irq_nr - IT8172_LPC_IRQ_BASE));
    }
    else if ((irq_nr >= IT8172_LB_IRQ_BASE)
            && (irq_nr <= IT8172_IOCHK_IRQ)) {
        // Local Bus interrupt
        It8172HwReg->lb_mask &= ~(1 << (irq_nr - IT8172_LB_IRQ_BASE));
    }
    else if ((irq_nr >= IT8172_PCI_DEV_IRQ_BASE)
            && (irq_nr <= IT8172_DMA_IRQ)) {
        // PCI and other interrupts
        It8172HwReg->pci_mask &= ~(1 << (irq_nr - IT8172_PCI_DEV_IRQ_BASE));
    }
    else if ((irq_nr >= IT8172_NMI_IRQ_BASE)
            && (irq_nr <= IT8172_POWER_NMI_IRQ)) {
        // NMI interrupts
        It8172HwReg->nmi_mask &= ~(1 << (irq_nr - IT8172_NMI_IRQ_BASE));
    }
    else {
        kprintf("EnableIt8172Irq: bad irq %d\n", irq_nr);
    }
}

void DoIrq(uint32_t uIrq, InterruptContext *pContext)
{
    assert(NULL != IRQ[uIrq].Isr);

    IRQ[uIrq].Isr(uIrq, IRQ[uIrq].pDevice, pContext);
}

void AddIt8172Device(unsigned int uIrq, isr_t Isr, void *pDevice)
{
    uint32_t uFlags;

    assert(0 != uIrq);
    assert(NULL != pDevice);
    assert(NULL == IRQ[uIrq].Isr);

    IRQ[uIrq].Isr = Isr;
    IRQ[uIrq].pDevice = pDevice;

    uFlags = SaveFlagsAndCli();
    EnableIt8172Irq(uIrq);
    RestoreIF(uFlags);
}
